#include "pch.hpp"
#include "Message.hpp"
#include "uuid.hpp"

#include "Commands.hpp"
#include "Responses.hpp"

#include <boostext/iostreams/bin_stream.hpp>
#include <boostext/iostreams/device/vector.hpp>

using namespace Engine;
using namespace Engine::Debugger;




Message::Message()
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

Message::Message(boost::uuids::uuid tag)
	: m_tag(tag)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

Message::~Message()
{}
///////////////////////////////////////////////////////////////////////////////////////////////////




boost::uuids::uuid Message::tag() const
{
	return m_tag;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Message::store(boostext::iostreams::bin_ostream& stream) const
{
	stream << m_tag;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Message::restore(boostext::iostreams::bin_istream& stream)
{
	stream >> m_tag;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




boostext::iostreams::bin_ostream& Engine::Debugger::operator<<(boostext::iostreams::bin_ostream& stream, const Message& message)
{
	// Start indicator
	stream << (uint16)0xAFFE;
	stream << static_cast<uint32>(message.type());

	// Then we store the length of the message:
	// To do that, we have to serialize it into a temporary buffer first
	std::vector<char> tmp;
	boostext::iostreams::bin_stream<boostext::iostreams::vector> buffer(tmp);
	buffer.setEndian(stream.endian());
	message.store(buffer);
	stream << static_cast<uint32>(buffer->size());

	// Now we rewind the temporary buffer to the beginning and write it into the target stream
	buffer.seek(std::streamsize(0));
	stream.write(buffer->directSequence(buffer->size()), buffer->size());

	// End indicator
	stream << (uint16)0xAFFE;

	return stream;
}
///////////////////////////////////////////////////////////////////////////////////////////

boostext::iostreams::bin_ostream& Engine::Debugger::operator<<(boostext::iostreams::bin_ostream& stream, Message::const_ptr message)
{
	stream << *message;
	return stream;
}
///////////////////////////////////////////////////////////////////////////////////////////

boostext::iostreams::bin_istream& Engine::Debugger::operator>>(boostext::iostreams::bin_istream& stream, Message::ptr& message)
{
	// The minimum size for a message is the following:
	// uint16 for 0xAFFE
	// uint32 for the type
	// uint32 for the size of the actual message (may be 0)
	// uint16 for 0xAFFE
	if(stream.left() < sizeof(uint16) + sizeof(uint32) + sizeof(uint32) + sizeof(uint16))
		throw not_enough_data();

	uint16 indicator;
	stream >> indicator;
	verify(indicator == 0xAFFE);

	// Read the type and create the right message instance
	uint32 type;
	stream >> type;

	switch(type)
	{
	case Message::Attach:          message = boost::make_shared<AttachCommand>();
	case Message::Continue:        message = boost::make_shared<ContinueCommand>();
	case Message::Pause:           message = boost::make_shared<PauseCommand>();
	case Message::Stop:            message = boost::make_shared<StopCommand>();
	case Message::Detach:          message = boost::make_shared<DetachCommand>();
	case Message::SetBreakpoint:   message = boost::make_shared<SetBreakpointCommand>();
	case Message::GetCallstack:    message = boost::make_shared<GetCallstackCommand>();
	case Message::GetVariable:     message = boost::make_shared<GetVariableCommand>();

	case Message::Response:        message = boost::make_shared<Response>();
	case Message::ReturnCallstack: message = boost::make_shared<CallstackResponse>();

	default: throw std::bad_typeid("Unknown type");
	}

	// Read the supposed length of the message
	uint32 length;
	stream >> length;

	auto pos = stream.pos();
	if(length > stream.left())
		throw not_enough_data();

	// Now we can restore the message
	message->restore(stream);

	// We have to make sure that the size that was required to store
	// the message is actually read now, otherwise there is a mismatch
	// in the streaming operators, and this should not happen
	verify(stream.pos() - pos == length);

	// And the indicator for the end of a message...
	stream >> indicator;
	verify(indicator == 0xAFFE);

	return stream;
}
///////////////////////////////////////////////////////////////////////////////////////////
